import demo
import math
################################################################################
#
#Copyright (c) 2011, Petr Skramovsky
#All rights reserved.
#
#Redistribution and use in source and binary forms, with or without
#modification, are permitted provided that the following conditions are met:
#    * Redistributions of source code must retain the above copyright
#      notice, this list of conditions and the following disclaimer.
#    * Redistributions in binary form must reproduce the above copyright
#      notice, this list of conditions and the following disclaimer in the
#      documentation and/or other materials provided with the distribution.
#
#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
#ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
#WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
#DISCLAIMED. IN NO EVENT SHALL Petr Skramovsky BE LIABLE FOR ANY
#DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
#(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
#LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
#ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
#(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
#SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
################################################################################

from q3 import entityState
from q3 import entityStateQz
import huffman
from q3 import playerStateNETF
from q3 import playerState
from q3 import *
import struct
import os.path
import sys
import re

class qbuffer():
  def __init__(self):
    pass

  def init( self ):
    return huffman.init()
    
  def read_byte(self):
    return huffman.readbyte()

  def read_short(self):
    return huffman.readshort()

  def read_long(self):
    return huffman.readlong()

  def read_float( self ):
    bs = ""
    for i in range( 4 ):
      bs += chr( self.read_byte() )
    return struct.unpack( "f",  bs )[ 0 ]

  def read_string( self ):
    return huffman.readstring()

  def read_big_string( self ):
    return huffman.readbigstring()

  def read_data( self, out, len ):
    for i in range( len ):
      out[ i ] = self.read_byte()

  def fill( self, count ):
    return huffman.fill( count )

  def open( self, file ):
    return huffman.open( file )

  def close( self ):
    return huffman.close()

  def read_raw_long( self ):
    return huffman.readrawlong()

  def tell( self ):
    return huffman.tell()

  def read_bits( self, bits ):
    return huffman.readbits( bits )

class Handler():
  def handle_player_state(self,state):
    pass
  def handle_entity_state(self,state):
    pass
  def handle_server_cmd(self,cmd):
    pass
  def handle_config_string(self,str):
    pass
  
class DemoReader():
  def __init__( self, path, handler ):
    self.handler = handler
    self.file_size = os.path.getsize( path )
    self.buffer = qbuffer()
    self.buffer.init()
    self.buffer.open( path )
    self.serverCommandSequence = -1
    self.serverCmdSeq = -1
    self.quakelive = False
    if path.endswith( ".dm_73" ):
      self.quakelive = True
    self.config = [ "" ] * MAX_CONFIGSTRINGS
    self.backupSnaps = [ None] * PACKET_BACKUP
    self.parseEntities = [ entityState() for i in range( MAX_PARSE_ENTITIES ) ]
    if self.quakelive :
      self.entityBaselines = [ entityStateQz() for i in range( MAX_GENTITIES ) ]
    else :
      self.entityBaselines = [ entityState() for i in range( MAX_GENTITIES ) ]

  def read( self ):
    self.serverMessageSequence = -1
    self.lastSnapTime = 0
    self.parseEntitiesNum = 0
    self.snapCount = 0
    while self.buffer.tell() < self.file_size :
      self.serverMessageSequence = self.buffer.read_raw_long()
      if self.serverMessageSequence == -1 :
        break
      self.read_block()
      if not self.parse_message() :
        break

  def read_block( self ):
    size = self.buffer.read_raw_long()
    if size == -1 :
      self.buffer = None
      return
    self.buffer.fill( size )

  def parse_message(self):
    reliableAcknowledge = self.buffer.read_long()
    while True :
      cmd= self.buffer.read_byte()
      if cmd == svc_EOF:
        return True
      elif cmd == svc_NOP :
        continue
      elif cmd == svc_gamestate :
        self.parse_gamestate()
        continue
      elif cmd == svc_serverCommand:
        self.handler.handle_server_cmd( self.parse_server_cmd() )
        continue
      elif cmd == svc_download :
        print "svc download"
        continue
      elif cmd == svc_snapshot :
        self.handler.handle_player_state( self.parse_snapshot() )
        continue
      else :
        print "svc unknown cmd : %d" % cmd
        return False
    return False

  def parse_snapshot( self ):
    serverTime = 0
    deltaNum = 0
    areamaskLen = 0
    old = None
    newSnap = clSnapshot()
    areamaskData = [ 0 ] * MAX_MAP_AREA_BYTES
    newSnap.serverCommandNum = self.serverCommandSequence
    serverTime = self.buffer.read_long()
    prevSnapTime = self.lastSnapTime
    newSnap.serverTime = serverTime
    newSnap.messageNum = self.serverMessageSequence
    deltaNum = self.buffer.read_byte()
    if deltaNum :
      newSnap.deltaNum = -1
      old = None
    else :
      newSnap.deltaNum = newSnap.messageNum - deltaNum
      old = self.backupSnaps[ newSnap.deltaNum & PACKET_MASK ]
    newSnap.snapFlags = self.buffer.read_byte()
    areamaskLen = self.buffer.read_byte()
    if areamaskLen > len( areamaskData ) :
      print "Incorrect areamask length %d" % areamaskLen
    self.buffer.read_data( areamaskData, areamaskLen )
    pl = self.parse_player_state()
    self.snapCount += 1
    self.parse_packet_entities( old, newSnap )
    self.backupSnaps[ newSnap.messageNum & PACKET_MASK ] = newSnap
    return pl

  def parse_player_state( self ):
    lastPlayerField = self.buffer.read_byte()
    player = playerState()
    netf = playerStateNETF( player )
    playerStateFieldsNum  = len( netf.bits )
    if lastPlayerField > playerStateFieldsNum :
      print "max allowed field num is %d" % playerStateFieldsNum
    for i in range( 0, lastPlayerField ) :
      if self.buffer.read_bits( 1 ) :
        if netf.bits[ i ] == 0 :
          if self.buffer.read_bits( 1 ) == 0 :
            netf.fields[ i ] = self.buffer.read_bits( FLOAT_INT_BITS ) - FLOAT_INT_BIAS
          else :
            netf.fields[ i ] = self.buffer.read_float()
        else :
          bits = netf.bits[ i ]
          netf.fields[ i ] = self.buffer.read_bits( bits )
    netf.update()
    if self.buffer.read_bits( 1 ) :
      if self.buffer.read_bits( 1 ) :
        c = self.buffer.read_short()
        for i in range( MAX_STATS ) :
          if c & ( 1 << i ) :
            player.stats[ i ] = self.buffer.read_short()
      if self.buffer.read_bits( 1 ) :
        c = self.buffer.read_short()
        for i in range( MAX_PERSISTANT ) :
          if c & ( 1 << i ) :
            player.persistant[ i ] = self.buffer.read_short()
      if self.buffer.read_bits( 1 ) :
        c = self.buffer.read_short()
        for i in range( MAX_WEAPONS ) :
          if c & ( 1 << i ) :
            player.ammo[ i ] = self.buffer.read_short()
      if self.buffer.read_bits( 1 ) :
        c = self.buffer.read_short()
        for i in range( MAX_POWERUPS ) :
          if c & ( 1 << i ) :
            player.powerups[ i ] = self.buffer.read_long()
    return player
    
  def parse_packet_entities( self, oldframe, newframe ):
    newnum = 0
    oldindex = 0
    oldnum = 0
    oldstate = None
    newframe.parseEntitiesNum = self.parseEntitiesNum
    newframe.numEntities = 0
    oldindex = 0
    oldstate = 0
    if not oldframe:
      oldnum = 99999
    else :
      if oldindex >= oldframe.numEntities :
        oldnum = 99999
    while( True ):
      newnum = self.buffer.read_bits( GENTITYNUM_BITS )
      if newnum == ( MAX_GENTITIES - 1 ) :
        break
      while oldnum < newnum:
        self.deltaEntity( newframe, oldnum, oldstate, True )
        oldindex += 1
        if oldindex >= oldframe.numEntities :
          oldnum = 99999
      if oldnum == newnum:
        self.deltaEntity( newframe, newnum, oldstate, False );
        oldindex += 1
        if oldindex >= oldframe.numEntities :
          oldnum = 99999
        continue
      if oldnum > newnum :
        baseline = self.entityBaselines[ newnum ]
        self.deltaEntity( newframe, newnum, baseline, False )
        continue
    while oldnum != 99999:
      self.deltaEntity( newframe, oldnum, oldstate, True );
      oldindex += 1
      if oldindex >= oldframe.numEntities :
        oldnum = 99999

  def deltaEntity( self, frame, newnum, old, unchanged ):
    state = self.parseEntities[ self.parseEntitiesNum & ( MAX_PARSE_ENTITIES - 1 ) ]
    if unchanged:
      state = old
    else:
      self.read_delta_entity( old, state, newnum )
    if state.number == ( MAX_GENTITIES - 1 ) :
      return
    self.parseEntitiesNum += 1
    frame.numEntities += 1

  def parse_server_cmd( self ):
    seq = self.buffer.read_long()
    cmd = self.buffer.read_string()
    return cmd
  
  def parse_gamestate( self ):
    self.serverCommandSequence = self.buffer.read_long()
    self.serverCmdSeq = 0
    while( True ):
      cmd = self.buffer.read_byte()
      if cmd == svc_EOF :
        break
      elif cmd == svc_configstring :
        self.handler.handle_config_string( self.parse_configstr() )
      elif cmd == svc_baseline :
        self.handler.handle_entity_state( self.parse_baseline() )
      else :
        print "bad cmd number"
        break
    clientNum = self.buffer.read_long()
    checksumFeed = self.buffer.read_long()

  def parse_configstr(self):
    index = self.buffer.read_short()
    str = self.buffer.read_big_string()
    return ( index, str )

  def parse_baseline( self ):
    newnum = self.buffer.read_bits( GENTITYNUM_BITS )
    if newnum < 0 or newnum >= MAX_GENTITIES :
      print "Baseline number out of range: %d\n" % newnum
    nstate = entityStateQz()
    baseline = self.entityBaselines[ newnum ]
    return self.read_delta_entity( nstate, baseline, newnum )

  def read_delta_entity( self, frm, to, number ):
    if self.buffer.read_bits( 1 ) == 1 :
      return
    if self.buffer.read_bits( 1 ) == 0 :
      return
    lastFieldNum = self.buffer.read_byte()
    ent = None
    netf = None
    if self.quakelive:
      ent = entityStateQz()
      netf = entityStateQzNETF( ent )
    else:
      ent = entityState()
      netf = entityStateNETF( ent )
    numFields = len( netf.bits )
    if lastFieldNum > numFields :
      print "Incorrect field count %d" % lastFieldNum
      return
    for i in range( 0, lastFieldNum ):
      if self.buffer.read_bits( 1 ) :
        if not netf.bits[ i ] :
          if self.buffer.read_bits( 1 ) != 0 :
            if self.buffer.read_bits( 1 ) == 0 :
              netf.fields[ i ] = self.buffer.read_bits( FLOAT_INT_BITS )
            else :
              netf.fields[ i ] = self.buffer.read_float()
        else:
          if self.buffer.read_bits( 1 ) != 0:
            bits = netf.bits[ i ]
            netf.fields[ i ] = self.buffer.read_bits( bits )
    netf.update()
    return ent
    
class Demo( Handler ):
  def __init__(self):
    self.config = {}
    self.playerid = 1
    self.pstates = []
    self.starttime = -1
    self.endtime = -1
    self.time = -1
    self.weapons = []

  def handle_config_string(self,str):
    n, str = str
    if str.startswith("n\\"):
      pc = self.config[ "player_id_%s" % self.playerid ] = {}
      sp = str.split("\\")
      for k, v in zip(sp[::2], sp[1::2]):
        pc[ k ] = re.sub("\^\d","",v)
      self.config[ "players_count" ] = self.playerid
      self.playerid += 1
    else:
      sp = str[1:].split("\\")
      for k, v in zip(sp[::2], sp[1::2]):
        self.config[ k ] = v
  
  def handle_player_state(self,state):
    self.pstates.append(state)
    if self.starttime == -1:
      self.starttime = state.commandTime
    self.endtime = state.commandTime
    self.time = self.endtime - self.starttime
    if state.weapon != 0 and state.weapon not in self.weapons:
      self.weapons.append(state.weapon)
  
  def handle_entity_state(self,state):
    #print state
    pass
  
  def handle_server_cmd(self,cmd):
    #print cmd
    pass

def read_demo(file):
  demo = Demo()
  dr = DemoReader( file, demo )
  dr.read()
  return demo

if __name__ == "__main__":
  df = "C:\\Program Files (x86)\\Quake III Arena\\defrag\\demos\\coldrun_TR[df.vq3]00.07.576(q3a.scripted).dm_68"
  demo = read_demo(df)

  print demo.config
  print demo.config["mapname"]
  print demo.config["player_id_1"]["model"]
  print demo.config[ "players_count" ]
  print demo.starttime
  print demo.endtime
  print demo.time
  print demo.weapons


